iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
自我挑戰組

Go in 3o系列 第 10

[Day10] Go in 30 - 空介面與型別檢查

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20230925/201626933IvCQGCYmE.png

(圖片來源:twitter @oleg_kovalov)

一、本篇重點

  • 型別轉換
  • 型別斷言
  • interface{}空介面
  • 型別switch
  • panic

二、型別轉換

在開發時遲早會遇上型別不一致的時候,而Go採用嚴格的型別系統(Go沒有隱性型別轉換implicit type conversion),此時我們有兩個選擇 :

  1. 做型別轉換(typed conversion) : 亦即將其中一個轉成另一個型別。
<型別>(<值>)

例如 :

從rune轉為string。

string([]rune)

從string轉為rune。

[]rune(string)

字串轉換之所以可行,是因為它是使用bytes切片來儲存資料。
因此,字串轉換不會損失。但是,其他型別轉換就不見得惹。

像是將 int64 轉 int8,或是uint轉成int64,由於儲存數字變小了,就有可能發生溢位!!!!
又或是把int轉成float也有可能溢位。
因為浮點數會把儲存空間分割成整數和小數位,至於將float轉成int,小數直接被截去。

** Note : 也有些Go核心型別之間無法轉換的,例如字串不能直接轉換成數字、布林值不能直接轉成字串或數字。**
不過 strconv 套件提供了一系列能將字串轉為可指定型別的功能

package main

import (
    "fmt"
)
func main() {
    int64Value := int64(300)
    int8Value := int8(int64Value)
    fmt.Println(int8Value)
}

在這個範例中,將一個int64值(300)轉換為int8,但int8只能表示-128到127之間的整數,因此溢位將會發生,結果將會是-56。

2.1 型別斷言 Type Assertion

到目前為止,使用了許多fmt.Println()來印出資料,然而為何這樣一個函式可以接收任何型別值做為參數呢?
我們可以看一下Go標準函式庫定義 :

func Print(a...interface{}) (n int, err error) {
    return Fprint(os.Stout, a...)
}
  • Print 函式接受可變數量的參數 a,這些參數的型別為 interface{},也就是可以接受任意型別的參數。

  • (n int, err error) 是函式的回傳值列表,其中 n 是輸出的位元組數,err 是可能的錯誤。

  • 函式內部使用了 Fprint 函式來執行實際的輸出運算,但輸出的目標是標準輸出 os.Stdout。

  • Fprint 函式的第一個參數是輸出目標,第二個參數是要輸出的內容,它使用 ... 語法表示可以接受可變數量的參數。

介面會在後續篇幅做為介紹,可以先知道介面的定義就是:

介面型別是一種規範,會列出若干函式的定義,而任何型別只要具備相同的函式,就會被視為符合該介面型別。
介面不會決定傳入值得變成什麼型別,它只會決定哪些型別符合資格
介面是一種抽象類型,可以容納不同底層類型的值。

類型斷言是在運行時檢查介面值的實際類型,並將其轉換為該類型。 這可以讓程式設計師安全地存取介面值的底層類型的屬性和方法。

2.2 斷言兩種形式

在某些語言中,類型斷言有兩種形式:型別斷言和型別斷言表達式。

  1. 型別斷言
    使用 .(Type) 的語法,其中 Type 是要斷言的目標類型。
    如果斷言成功,它會傳回目標類型的值和一個標誌來指示成功;如果斷言失敗,它會引發執行時間錯誤或傳回零值。
<值> := <變數名稱>.(<型別>)

此時你還可以接收第二個選擇性回傳值,代表轉成功與否 :

<值>, <ok> := <變數名稱>.(<型別>)

如果不接收第二回傳值的話,而斷言失敗,Go語言會引發panic

  1. 型別斷言表達式
    使用 x.(type) 的語法,其中 x 是介面類型的值。 它通常與 switch 語句一起使用,可以根據介面值的實際類型執行不同的操作。

下面是一個使用Go語言中的類型斷言的範例:

func printLength(x interface{}) {
    if str, ok := x.(string); ok {
        fmt.Printf("Length of string: %d\n", len(str))
    } else if arr, ok := x.([]int); ok {
        fmt.Printf("Length of slice: %d\n", len(arr))
    } else {
        fmt.Println("Unknown type")
    }
}

func main() {
    s := "Hello, World!"
    a := []int{1, 2, 3, 4, 5}

    printLength(s) // Length of string: 13
    printLength(a) // Length of slice: 5
}

空介面的用處 ?

答案是沒有用處,但它仍然是個可以傳給函式的值,

那些型別符合空介面?

答案是~所有型別,必然符合空介面的型別。

只要結合interface{} 和型別斷言,就可以幫你繞過Go語言嚴格型別控制,讓你建立可接受任何型別做為參數的函式。缺點就是會失去Go提供的安全型別保護,此時確保型別安全的的責任,就是你扛啦。

switch 搭配型別斷言

前幾篇有提到switch的部分,當我們將switch結合型別斷言,它就叫做type switch 啦!
此時,型別的switch只會執行符合型別的case所對應的程式敘述,並把值設定成那個型別。
我們可以在case比對一種以上的型別,但這樣Go就無法幫你自動調整值型別,我們還是得在case底下加入額外的型別斷言。

package main

import (
    "fmt"
)

func main() {

    var val interface{} 

    val = 42 

    switch val.(type) {
    case int:
        fmt.Println("val is int")
    case string:
        fmt.Println("val is string")
    default:
        fmt.Println("val not int nor string")
    }
}

函式範例 :

package main

import (
     "fmt"
)

func processValue(val interface{}) {
     switch val.(type) {
     case int:
         fmt.Println("val 是一個整數")
     case string:
         fmt.Println("val 是一個字串")
     default:
         fmt.Println("val 不是整數也不是字串")
     }
}

func main() {
     var val interface{} // 定義一個空介面類型的變數

     val = 42 // 將一個整數賦值給空介面
     processValue(val)

     val = "Hello, World!" // 將一個字串賦值給空介面
     processValue(val)

     val = 3.14 // 將一個浮點數賦值給空介面
     processValue(val)
}

補充: 關於Panic

在 Go 語言中,panic 是一種執行時間錯誤,用於表示程式發生了一個嚴重的錯誤,通常是由於不可恢復的錯誤或異常情況導致的。 當程式執行到一個 panic 語句時,它會立即中斷目前的執行流程,並開始執行程式的 panic 處理流程。

以下是關於 panic 的一些重要資訊和用法:

**觸發 Panic: **可以使用 panic 函數明確觸發 panic,也可以在某些情況下由執行時自動觸發。 例如,陣列越界、除以零等情況都會導致自動觸發 panic。

**Panic 處理: **當程式觸發 panic 時,它會立即停止目前的執行流程,然後開始執行 panic 處理流程。 這包括運行程式中的 deferred 函數(透過 defer 聲明的函數)和可能的 recover 操作。

**Recover: **recover 是用於捕獲 panic 並進行處理的內建函數。 它只能在 deferred 函數內部使用。 recover 的作用是終止 panic 過程,並傳回 panic 值(如果有的話)。 如果沒有 panic,recover 將傳回 nil。 透過 recover,可以在程式發生 panic 時執行一些清理工作或進行錯誤處理,從而避免程式崩潰。

以下是一個範例,演示了 panic 和 recover 的使用:

package main

import (
     "fmt"
)

func main() {
     defer func() {
         if r := recover(); r != nil {
             fmt.Println("Recovered from panic:", r)
         }
     }()

     fmt.Println("Start of the program")

     // 觸發 panic
     panic("A serious error occurred")

     fmt.Println("End of the program") // 這行程式碼不會執行
}

在這個範例中,我們使用 defer 來延遲執行一個函數,該函數包含了 recover。 當程式觸發 panic 時,recover 將捕獲 panic,列印錯誤訊息,並使程式繼續執行,而不會崩潰。

需要注意的是,通常情況下,不建議濫用 panic 和 recover,應該盡量避免觸發 panic,而是透過錯誤處理來處理各種異常情況。 只有在特定情況下,如不可恢復的錯誤,或在庫中需要執行清理操作時,才應該使用 panic 和 recover。

以上就是今天的內容,接下來的主題將會進入完整滿滿的函式的介紹~~~~


上一篇
[Day09] Go in 30 - 替自訂型別(custom types)加上方法(method)
下一篇
[Day11] Go in 30 - 函式- 簡介
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言